#include "qelib.h"
#include "phy.h"
#include "miiphy.h"
#include "jdt.h"

#if (CONFIG_PSU_GEM == 1)

#define GEM_LOG_DOMAIN      "psu-gem"
#define gem_debug(...)      qelog_debug(GEM_LOG_DOMAIN, __VA_ARGS__)
#define gem_info(...)       qelog_info(GEM_LOG_DOMAIN, __VA_ARGS__)
#define gem_warning(...)    qelog_warning(GEM_LOG_DOMAIN, __VA_ARGS__)
#define gem_error(...)      qelog_error(GEM_LOG_DOMAIN, __VA_ARGS__)



/* Bit/mask specification */
#define PS7_GEM_PHYMNTNC_OP_MASK    0x40020000 /* operation mask bits */
#define PS7_GEM_PHYMNTNC_OP_R_MASK	0x20000000 /* read operation */
#define PS7_GEM_PHYMNTNC_OP_W_MASK	0x10000000 /* write operation */
#define PS7_GEM_PHYMNTNC_PHYAD_SHIFT_MASK	23 /* Shift bits for PHYAD */
#define PS7_GEM_PHYMNTNC_PHREG_SHIFT_MASK	18 /* Shift bits for PHREG */

#define PS7_GEM_RXBUF_EOF_MASK		0x00008000 /* End of frame. */
#define PS7_GEM_RXBUF_SOF_MASK		0x00004000 /* Start of frame. */
#define PS7_GEM_RXBUF_LEN_MASK		0x00003FFF /* Mask for length field */

#define PS7_GEM_RXBUF_WRAP_MASK	    0x00000002 /* Wrap bit, last BD */
#define PS7_GEM_RXBUF_NEW_MASK		0x00000001 /* Used bit.. */
#define PS7_GEM_RXBUF_ADD_MASK		0xFFFFFFFC /* Mask for addres */

/* Wrap bit, last descriptor */
#define PS7_GEM_TXBUF_WRAP_MASK	    0x40000000
#define PS7_GEM_TXBUF_LAST_MASK	    0x00008000 /* Last buffer */
#define PS7_GEM_TXBUF_USED_MASK	    0x80000000 /* Used by Hw */

#define PS7_GEM_NWCTRL_TXEN_MASK	0x00000008 /* Enable transmit */
#define PS7_GEM_NWCTRL_RXEN_MASK	0x00000004 /* Enable receive */
#define PS7_GEM_NWCTRL_MDEN_MASK	0x00000010 /* Enable MDIO port */
#define PS7_GEM_NWCTRL_STARTTX_MASK	0x00000200 /* Start tx (tx_go) */

#define PS7_GEM_NWCFG_SPEED100		0x00000001 /* 100 Mbps operation */
#define PS7_GEM_NWCFG_SPEED1000	    0x00000400 /* 1Gbps operation */
#define PS7_GEM_NWCFG_FDEN		    0x00000002 /* Full Duplex mode */
#define PS7_GEM_NWCFG_FSREM		    0x00020000 /* FCS removal */
#define PS7_GEM_NWCFG_SGMII_ENBL	0x08000000 /* SGMII Enable */
#define PS7_GEM_NWCFG_PCS_SEL		0x00000800 /* PCS select */
#ifdef CONFIG_ARM64
#define PS7_GEM_NWCFG_MDCCLKDIV	    0x00100000 /* Div pclk by 64, max 160MHz */
#else
#define PS7_GEM_NWCFG_MDCCLKDIV	    0x000c0000 /* Div pclk by 48, max 120MHz */
#endif

#ifdef CONFIG_ARM64
# define PS7_GEM_DBUS_WIDTH	        (1 << 21) /* 64 bit bus */
#else
# define PS7_GEM_DBUS_WIDTH	        (0 << 21) /* 32 bit bus */
#endif

#define PS7_GEM_NWCFG_INIT		(PS7_GEM_DBUS_WIDTH | \
					PS7_GEM_NWCFG_FDEN | \
					PS7_GEM_NWCFG_FSREM | \
					PS7_GEM_NWCFG_MDCCLKDIV)

#define PS7_GEM_NWSR_MDIOIDLE_MASK	0x00000004 /* PHY management idle */

#define PS7_GEM_DMACR_BLENGTH		0x00000004 /* INCR4 AHB bursts */
/* Use full configured addressable space (8 Kb) */
#define PS7_GEM_DMACR_RXSIZE		0x00000300
/* Use full configured addressable space (4 Kb) */
#define PS7_GEM_DMACR_TXSIZE		0x00000400
/* Set with binary 00011000 to use 1536 byte(1*max length frame/buffer) */
#define PS7_GEM_DMACR_RXBUF		0x00180000

#define PS7_GEM_DMACR_INIT		(PS7_GEM_DMACR_BLENGTH | \
					PS7_GEM_DMACR_RXSIZE | \
					PS7_GEM_DMACR_TXSIZE | \
					PS7_GEM_DMACR_RXBUF)

#define PS7_GEM_TSR_DONE		0x00000020 /* Tx done mask */

#define PS7_GEM_PCS_CTL_ANEG_ENBL	0x1000

/* Use MII register 1 (MII status register) to detect PHY */
#define PHY_DETECT_REG  1

/* Mask used to verify certain PHY features (or register contents)
 * in the register above:
 *  0x1000: 10Mbps full duplex support
 *  0x0800: 10Mbps half duplex support
 *  0x0008: Auto-negotiation support
 */
#define PHY_DETECT_MASK 0x1808



typedef struct 
{
    qe_u32 addr;
    qe_u32 status;
} gem_bd;

typedef struct 
{
    qe_u32 nwctrl;      /* 0x00 Network Control Register */ 
    qe_u32 nwcfg;       /* 0x04 Network Configuration Register */
    qe_u32 nwsr;        /* 0x08 Network Status Register */
    qe_u32 reserved1;
    qe_u32 dmacr;       /* 0x10 DMA Control Register */
    qe_u32 txsr;        /* 0x14 Tx Status Register */
    qe_u32 rxqbase;     /* 0x18 Rx Queue Base Address Register */
    qe_u32 txqbase;     /* 0x1C Tx Queue Base Address Register */
    qe_u32 rxsr;        /* 0x20 Rx Status Register */
    qe_u32 reserved2[2];
    qe_u32 idr;         /* 0x28 Interrupt Disable Register */
    qe_u32 reserved3;
    qe_u32 phymntnc;    /* 0x30 PHY Maintenance Register */
    qe_u32 reserved4[18];
    qe_u32 hashl;       /* 0x80 Hash Table Low Register */
    qe_u32 hashh;       /* 0x84 Hash Table High Register */
    qe_u32 laddr[4][2]; /* 0x88 Special Address Register */
    qe_u32 match[4];
    qe_u32 reserved5[18];
    qe_u32 stat[44];    /* 0x100 Octects transmitted Low reg */
    qe_u32 reserved6[20];
    qe_u32 pcscntrl;    /* 0x200 PCS Control Register */
    qe_u32 reserved7[143];
    qe_u32 transmit_q1_ptr; /* 0x400 Transmit priority Queue 1 Pointer */
    qe_u32 reserved8[15];
    qe_u32 receive_q1_ptr;  /* 0x480 Receive priority Queue 1 Pointer */ 
} gem_regs;

typedef struct 
{
    qe_dev dev;
    gem_bd *rx_bds;
    gem_bd *tx_bds;
    gem_regs *regs;
    mii_dev *bus;
    phy_dev *phy;
    qe_int phyaddr;
    qe_int irq;
    phy_interface_mode interface;
} ps7_gem;



static qe_bool wait_for_bit(qe_u32 *val, qe_u32 mask, qe_u32 timeout_us)
{
    while (timeout_us--) {
        if (*val & mask) {
            return qe_true;
        }
        qe_usleep(1);
    }

    return qe_false;
}

static qe_ret gem_phy_setup_op(ps7_gem *gem, qe_u32 phyaddr, qe_u32 reg, qe_u32 op, qe_u16 *val)
{
    qe_u32 mgtcr;
    gem_regs *regs = gem->regs;

    if (!wait_for_bit(&regs->nwsr, PS7_GEM_NWSR_MDIOIDLE_MASK, 20000)) {
        return qe_err_common;
    }

    /* Construct mgtcr mask for the operation */
	mgtcr = PS7_GEM_PHYMNTNC_OP_MASK | op |
		(phyaddr << PS7_GEM_PHYMNTNC_PHYAD_SHIFT_MASK) |
		(reg << PS7_GEM_PHYMNTNC_PHREG_SHIFT_MASK) | *val;
    
    /* Write mgtcr and wait for completion */
    regs->phymntnc = mgtcr;

    if (!wait_for_bit(&regs->nwsr, PS7_GEM_NWSR_MDIOIDLE_MASK, 20000)) {
        return qe_err_common;
    }

    if (op == PS7_GEM_PHYMNTNC_OP_R_MASK) {
        *val = regs->phymntnc;
    }

    return qe_ok;
}

static qe_ret gem_phy_read(ps7_gem *gem, qe_u32 phyaddr, qe_u32 reg, qe_u16 *val)
{
    qe_ret ret;
    ret = gem_phy_setup_op(gem, phyaddr, reg,
        PS7_GEM_PHYMNTNC_OP_R_MASK, val);
    return ret;
}

static qe_ret gem_phy_write(ps7_gem *gem, qe_u32 phyaddr, qe_u32 reg, qe_u16 val)
{
    return gem_phy_setup_op(gem, phyaddr, reg, 
        PS7_GEM_PHYMNTNC_OP_W_MASK, &val);
}

static qe_ret gem_phy_detect(ps7_gem *gem)
{
    int i;
    qe_u16 phyreg = 0;

    if (gem->phyaddr != -1) {
        gem_phy_read(gem, gem->phyaddr, PHY_DETECT_REG, &phyreg);
        if ((phyreg != 0xFFFF) &&
            (phyreg & PHY_DETECT_MASK) == PHY_DETECT_MASK) {
            gem_debug("default phy address %d is valid", gem->phyaddr);
            return qe_ok;
        } else {
            gem_debug("default phy address %d is invalid", gem->phyaddr);
            gem->phyaddr = -1;
        }
    }

    gem_debug("detecting phy address");
    if (gem->phyaddr == -1) {
        for (i = 31; i >= 0; i--) {
            gem_phy_read(gem, i, PHY_DETECT_REG, &phyreg);
            if ((phyreg != 0xFFFF) &&
                (phyreg & PHY_DETECT_MASK) == PHY_DETECT_MASK) {
                gem->phyaddr = i;
                gem_debug("found valid phy address %d", gem->phyaddr);
                return qe_ok;
            }
        }
    }
    gem_info("phy not detected");
    return qe_err_notfind;
}

static qe_ret gem_phy_init(ps7_gem *gem)
{
    qe_ret ret;
    gem_regs *regs = gem->regs;
    const qe_u32 supported = 
        SUPPORTED_10baseT_Half |
        SUPPORTED_10baseT_Full |
        SUPPORTED_100baseT_Half |
        SUPPORTED_100baseT_Full |
        SUPPORTED_1000baseT_Half |
        SUPPORTED_1000baseT_Full;

    /* Enable only MDIO bus */
    regs->nwctrl = PS7_GEM_NWCTRL_MDEN_MASK;

    if (gem->interface != PHY_INTERFACE_MODE_SGMII &&
        gem->interface != PHY_INTERFACE_MODE_GMII) {
        ret = gem_phy_detect(gem);
        if (ret != qe_ok) {
            gem_error("phy detect error:%d", ret);
            return ret;
        }
    }

    gem->phy = phy_connect(gem->bus, gem->phyaddr, gem->interface);
    if (!gem->phy) {
    	gem_error("phy connect failed");
    	return qe_err_common;
    }

    return phy_config(gem->phy);
}

static int gem_mii_read(mii_dev *bus, int addr, int reg)
{
    qe_u16 val;
    qe_ret ret;
    ps7_gem *gem = (ps7_gem *)bus->priv;
    ret = gem_phy_read(gem, addr, reg, &val);
    gem_debug("read 0x%x 0x%x 0x%x %d", addr, reg, val, ret);
    return val;
}

static qe_ret gem_mii_write(mii_dev *bus, int addr, int reg, qe_u16 val)
{
    ps7_gem *gem = (ps7_gem *)bus->priv;
    gem_debug("write 0x%x 0x%x %d", addr, reg, val);
    return gem_phy_write(gem, addr, reg, val);
}

static qe_init int ps7_gem_init(void)
{
    yyjson_val *node;
    yyjson_val *name;
    yyjson_val *regs;
    yyjson_val *irq;

    qelog_domain_set_level(GEM_LOG_DOMAIN, QELOG_DEBUG);

    node = jdt_find_node("gem");
    if (!node) {
        gem_warning("gem not found");
        return 0;
    }

    name = yyjson_obj_get(node, "name");
    regs = yyjson_obj_get(node, "regs");
    irq  = yyjson_obj_get(node, "irq");

    if (!name) {
        gem_error("no attr name");
    }

    if (!regs) {
        gem_error("no attr regs");
    }

    if (!irq) {
        gem_error("no attr irq");
    }

    ps7_gem *gem = qe_malloc(sizeof(ps7_gem));
    qe_assert(gem);

    gem->regs = (gem_regs *)qe_hexstr_to_u32(yyjson_get_str(regs));
    gem->irq  = yyjson_get_int(irq);
    gem->phyaddr = -1;
    gem_info("regs:0x%x irq:%d", gem->regs, gem->irq);

    gem->bus = mdio_alloc();
    gem->bus->read = gem_mii_read;
    gem->bus->write = gem_mii_write;
    gem->bus->priv = (void *)gem;

    gem_phy_init(gem);

    return 0;
}
QE_DEVICE_EXPORT(ps7_gem_init);

#endif
